﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Windows;
using System.ComponentModel;
using System.Globalization;
using IVS.MathLibrary;

namespace IVS.Calculator
{
    /// <summary>
    /// Represents type of mathematical operation.
    /// </summary>
    public enum Operations
    {
        None,
        Restart,
        Addition,
        Subtraction,
        Multiplication,
        Division,
        Power,
        SquareRoot,
        Factorial,
        Inversion,
        Negation,
    }

    /// <summary>
    /// Application logic for MainWindow.
    /// </summary>
    public class MainWindowPresenter
    {
        #region ------------------------------ CONSTRUCTION ------------------------------

        /// <summary>
        /// Creates new instance of presenter.
        /// </summary>
        public MainWindowPresenter ()
        {
            _currentNumber = 0;
            _display = "0";
            _currentOperation = Operations.None;
            _math = new Arithmetic();
        }

        #endregion

        #region ------------------------------ FIELDS ------------------------------

        Arithmetic _math;

        Operations _currentOperation;
        string _display;
        double _currentNumber;
        double _memory;

        double _binaryOperator1;
        double _binaryOperator2;

        const string _errorMsg = "Error!";

        #endregion

        #region ------------------------------ PROPERTIES ------------------------------

        /// <summary>
        /// Gets current content of display.
        /// </summary>
        public string Display
        {
            get { return _display; }
            private set
            {
                if (value == _errorMsg)
                {
                    _currentNumber = 0;
                    _display = value;
                    FireDisplayChanged();
                    return;
                }

                value = value.Replace(',', '.');
                try
                {
                    _currentNumber = Double.Parse(value, CultureInfo.InvariantCulture);
                }
                catch
                {
                    return;
                }
                _display = value.FormatToDisplay(_currentNumber);
                if (_display == null)
                    _display = _errorMsg;
                FireDisplayChanged();
            }
        }

        /// <summary>
        /// Gets or sets curretly processed mathematical operation.
        /// </summary>
        public Operations CurrentOperation
        {
            get { return _currentOperation; }
            set
            {
                _currentOperation = value;

                // start again
                if (value == Operations.Restart)
                    _display = "0";

                // current operation is unary
                else if (value.IsUnary())
                    ShowResult();

                // current operation is binary
                else if (value.IsBinary())    
                {
                    _binaryOperator1 = _currentNumber;
                    _display = "0";
                    _currentNumber = 0;
                }

            }
        }

        #endregion

        #region ------------------------------ PUBLIC METHODS ------------------------------

        /// <summary>
        /// Adds character to calculator display.
        /// Only digits and decimal point are allowed.
        /// </summary>
        /// <param name="digit">digit to add</param>
        public void HandleNumberButton (char digit)
        {
            if (Char.IsDigit(digit) || digit == '.')
            {
                if (Display == _errorMsg)
                    Display = "0";

                if (Display == "0")
                {
                    if (digit != '.')
                        Display = digit.ToString();
                    else
                        Display += digit.ToString();
                }
                else
                {
                    if (digit != '.')
                        Display += digit.ToString();
                    else if (digit == '.' && !Display.Contains("."))
                        Display += digit.ToString();
                }
            }
        }

        /// <summary>
        /// Adds comma character to calculator display.
        /// Represents decimal point.
        /// </summary>
        public void HandleCommaButton()
        {
            HandleNumberButton('.');
        }

        /// <summary>
        /// Sets current operation according to given operation and shows
        /// result if previous operation was not displayed.
        /// </summary>
        /// <param name="operation">operation associated with operator button</param>
        public void HandleOperatorButton (Operations operation)
        {
            if (CurrentOperation.IsBinary())
                ShowResult();

            CurrentOperation = operation;
        }

        /// <summary>
        /// Shows result of mathematic operation to display.
        /// </summary>
        public void ShowResult ()
        {
            // defensive statement
            if (!CurrentOperation.IsBinary() && !CurrentOperation.IsUnary())
                return;

            double result = 0;
            _binaryOperator2 = _currentNumber;

            switch (CurrentOperation)
            {
                // unary operations
                case Operations.SquareRoot:
                    result = _math.SquareRoot(_currentNumber);
                    break;

                case Operations.Factorial:
                    result = _math.Factorial(_currentNumber);
                    break;

                case Operations.Inversion:
                    result = _math.Inversion(_currentNumber);
                    break;

                case Operations.Negation:
                    result = _math.Negation(_currentNumber);
                    break;

                // binary operations
                case Operations.Addition:
                    result = _math.Addition(_binaryOperator1, _binaryOperator2);
                    break;

                case Operations.Subtraction:
                    result = _math.Subtraction(_binaryOperator1, _binaryOperator2);
                    break;

                case Operations.Multiplication:
                    result = _math.Multiplication(_binaryOperator1, _binaryOperator2);
                    break;

                case Operations.Division:
                    result = _math.Division(_binaryOperator1, _binaryOperator2);
                    break;

                case Operations.Power:
                    result = _math.Power(_binaryOperator1, _binaryOperator2);
                    break;

                default:
                    break;
            }

            if (Double.IsNaN(result))
                Display = _errorMsg;

            Display = result.ToString();

            CurrentOperation = Operations.Restart;
        }

        /// <summary>
        /// Clears display. Cancels currently started mathematic operation.
        /// </summary>
        public void ClearDisplay ()
        {
            Display = "0";
            CurrentOperation = Operations.Restart;
        }

        /// <summary>
        /// Store current displayed value to memory.
        /// Re-writes previous value in memory.
        /// </summary>
        public void StoreToMemory ()
        {
            if (!Double.IsNaN(_currentNumber))
                _memory = _currentNumber;
            CurrentOperation = Operations.Restart;
        }

        /// <summary>
        /// Clear internal memory.
        /// </summary>
        public void ClearMemory ()
        {
            _memory = 0;
            CurrentOperation = Operations.Restart;
        }

        /// <summary>
        /// Shows value in internal memory to display.
        /// </summary>
        public void ShowMemory ()
        {
            _currentNumber = _memory;
            Display = _memory.ToString();
            if (CurrentOperation == Operations.Restart || CurrentOperation == Operations.None)
                CurrentOperation = Operations.Restart;
        }

        /// <summary>
        /// Adds current value of display to memory.
        /// </summary>
        public void AddToMemory ()
        {
            if (!Double.IsNaN(_currentNumber))
                _memory += _currentNumber;
            CurrentOperation = Operations.Restart;
        }

        /// <summary>
        /// Subtracts current value of display from memory.
        /// </summary>
        public void SubtractFromMemory ()
        {
            if (!Double.IsNaN(_currentNumber))
                _memory -= _currentNumber;
            CurrentOperation = Operations.Restart;
        }

        #endregion

        #region ------------------------------ PRIVATE METHODS ------------------------------


        /// EVENT CALLBACKS

        #endregion

        #region ------------------------------ EVENTS ------------------------------

        // DISPLAY CHANGED
        public event EventHandler DisplayChanged;
        protected virtual void FireDisplayChanged ()
        {
            EventHandler handler = DisplayChanged;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }

        #endregion
    }
}
